package com.reebake.ideal.workflow.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import com.reebake.ideal.workflow.exception.AssigneeNotMatchException;
import com.reebake.ideal.workflow.exception.HistoricActivityNotFoundException;
import com.reebake.ideal.workflow.service.WorkflowService;
import lombok.SneakyThrows;
import org.camunda.community.rest.client.api.*;
import org.camunda.community.rest.client.dto.*;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.File;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DefaultWorkflowService implements WorkflowService {
    @Autowired
    private DeploymentApi deploymentApi;
    @Autowired
    private ProcessDefinitionApi processDefinitionApi;
    @Autowired
    private ProcessInstanceApi processInstanceApi;
    @Autowired
    private TaskApi taskApi;
    @Autowired
    private TaskLocalVariableApi taskLocalVariableApi;
    @Autowired
    private HistoricActivityInstanceApi historicActivityInstanceApi;

    private Map<String, VariableValueDto> toVariables(Map<String, Object> variables) {
        if(MapUtil.isNotEmpty(variables)) {
            Map<String, VariableValueDto> variableValues = new HashMap<>();
            for(Map.Entry<String, Object> entry : variables.entrySet()) {
                Object value = entry.getValue();
                if(ObjUtil.isNull(value)) {
                    continue;
                }
                String key = entry.getKey();
                VariableValueDto variableValueDto = new VariableValueDto();

                variableValueDto.setValue(value);
                variableValueDto.setType(value.getClass().getSimpleName());
                variables.put(key, variableValueDto);
            }
            return variableValues;
        }
        return MapUtil.empty();
    }


    @Override
    @SneakyThrows
    public void deploy(String deploymentName, File data) {
        String tenantId = null;
        String deploymentSource = null;
        Boolean deployChangedOnly = null;
        Boolean enableDuplicateFiltering = null;
        OffsetDateTime deploymentActivationTime = null;

        deploymentApi.createDeployment(tenantId, deploymentSource, deployChangedOnly, enableDuplicateFiltering, deploymentName, deploymentActivationTime, data);
    }

    @Override
    @SneakyThrows
    public String start(String processDefinitionKey, Map<String, Object> variables) {
        StartProcessInstanceDto startProcessInstanceDto = new StartProcessInstanceDto();
        startProcessInstanceDto.setVariables(toVariables(variables));
        ProcessInstanceWithVariablesDto processInstanceWithVariablesDto = processDefinitionApi.startProcessInstanceByKey(processDefinitionKey, startProcessInstanceDto);
        return processInstanceWithVariablesDto.getId();
    }

    @Override
    @SneakyThrows
    public void discard(String processInstanceId) {
        Boolean skipCustomListeners = null;
        Boolean skipIoMappings = null;
        Boolean skipSubprocesses = null;
        Boolean failIfNotExists = true;
        processInstanceApi.deleteProcessInstance(processInstanceId, skipCustomListeners, skipIoMappings, skipSubprocesses, failIfNotExists);
    }

    @SneakyThrows
    private List<HistoricActivityInstanceDto> queryUserTaskActivityByProcessInstance(String processInstanceId) {
        HistoricActivityInstanceQueryDto historicActivityInstanceQueryDto = new HistoricActivityInstanceQueryDto();
        HistoricActivityInstanceQueryDtoSortingInner sortingInner = new HistoricActivityInstanceQueryDtoSortingInner();
        sortingInner.setSortBy(HistoricActivityInstanceQueryDtoSortingInner.SortByEnum.ENDTIME);
        sortingInner.setSortOrder(HistoricActivityInstanceQueryDtoSortingInner.SortOrderEnum.ASC);
        historicActivityInstanceQueryDto.processInstanceId(processInstanceId).activityType("userTask")
                .finished(true)
                .addSortingItem(sortingInner)
                ;
        return historicActivityInstanceApi.queryHistoricActivityInstances(null, null, historicActivityInstanceQueryDto);
    }

    public HistoricActivityInstanceDto getLastUserTaskActivity(String processInstanceId) {
        List<HistoricActivityInstanceDto> historicActivityInstances = queryUserTaskActivityByProcessInstance(processInstanceId);
        if(CollUtil.isEmpty(historicActivityInstances)) {
            throw new HistoricActivityNotFoundException();
        }
        // 最后一个节点即是上个节点
        return historicActivityInstances.get(historicActivityInstances.size() - 1);
    }

    public HistoricActivityInstanceDto getFirstUserTaskActivity(String processInstanceId) {
        List<HistoricActivityInstanceDto> historicActivityInstances = queryUserTaskActivityByProcessInstance(processInstanceId);
        if(CollUtil.isEmpty(historicActivityInstances)) {
            throw new HistoricActivityNotFoundException();
        }
        // 最后一个节点即是上个节点
        return historicActivityInstances.get(0);
    }

    private static ProcessInstanceModificationInstructionDto getProcessInstanceModificationInstructionDto(String toActivityId, String reason) {
        ProcessInstanceModificationInstructionDto processInstanceModificationInstructionDto = new ProcessInstanceModificationInstructionDto();
        processInstanceModificationInstructionDto.setCancelCurrentActiveActivityInstances(true);
        processInstanceModificationInstructionDto.setType(ProcessInstanceModificationInstructionDto.TypeEnum.STARTBEFOREACTIVITY);
        processInstanceModificationInstructionDto.setActivityId(toActivityId);
        TriggerVariableValueDto variableValueDto = new TriggerVariableValueDto();
        variableValueDto.setType("String");
        variableValueDto.setLocal(true);
        variableValueDto.setValue(reason);
        processInstanceModificationInstructionDto.setVariables(variableValueDto);
        return processInstanceModificationInstructionDto;
    }

    @SneakyThrows
    private void jump(String processInstanceId, String toActivityId, String reason) {
        ProcessInstanceModificationDto processInstanceModificationDto = new ProcessInstanceModificationDto();
        processInstanceModificationDto.annotation("withdraw");
        ProcessInstanceModificationInstructionDto processInstanceModificationInstructionDto = getProcessInstanceModificationInstructionDto(toActivityId, reason);
        processInstanceModificationDto.setInstructions(CollUtil.newArrayList(processInstanceModificationInstructionDto));

        processInstanceApi.modifyProcessInstance(processInstanceId, processInstanceModificationDto);
    }

    @Override
    @SneakyThrows
    public void complete(String taskId, Map<String, Object> localVariables) {
        CompleteTaskDto completeTaskDto = new CompleteTaskDto();
        Map<String, VariableValueDto> variableValues = toVariables(localVariables);
        for(Map.Entry<String, VariableValueDto> entry : variableValues.entrySet()) {
            taskLocalVariableApi.putTaskLocalVariable(taskId, entry.getKey(), entry.getValue());
        }
        taskApi.complete(taskId, completeTaskDto);
    }

    @SneakyThrows
    @Override
    public void withdraw(String processInstanceId, String assignee) {
        HistoricActivityInstanceDto lastActivityInstance = getLastUserTaskActivity(processInstanceId);
        if(!assignee.equals(lastActivityInstance.getAssignee())) {
            throw new AssigneeNotMatchException();
        }
        String toActivityId = lastActivityInstance.getActivityId();

        jump(processInstanceId, toActivityId, null);
    }

    @SneakyThrows
    @Override
    public void reject(String taskId, String assignee, Map<String, Object> localVariables) {
        TaskDto task = taskApi.getTask(taskId);
        if(!assignee.equals(task.getAssignee())) {
            throw new AssigneeNotMatchException();
        }
        Map<String, VariableValueDto> variableValues = toVariables(localVariables);
        for(Map.Entry<String, VariableValueDto> entry : variableValues.entrySet()) {
            taskLocalVariableApi.putTaskLocalVariable(taskId, entry.getKey(), entry.getValue());
        }
        String processInstanceId = task.getProcessInstanceId();
        HistoricActivityInstanceDto lastActivity= getLastUserTaskActivity(processInstanceId);
        String toActivityId = lastActivity.getActivityId();
        jump(processInstanceId, toActivityId, null);
    }

    @SneakyThrows
    @Override
    public void rejectToFirst(String taskId, String assignee, Map<String, Object> localVariables) {
        TaskDto task = taskApi.getTask(taskId);
        if(!assignee.equals(task.getAssignee())) {
            throw new AssigneeNotMatchException();
        }
        Map<String, VariableValueDto> variableValues = toVariables(localVariables);
        for(Map.Entry<String, VariableValueDto> entry : variableValues.entrySet()) {
            taskLocalVariableApi.putTaskLocalVariable(taskId, entry.getKey(), entry.getValue());
        }
        String processInstanceId = task.getProcessInstanceId();
        HistoricActivityInstanceDto lastActivity= getFirstUserTaskActivity(processInstanceId);
        String toActivityId = lastActivity.getActivityId();
        jump(processInstanceId, toActivityId, null);
    }
}
